﻿
namespace CmdArgs.Validation
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using CmdArgs.Utils;

    /// <summary>
    /// Result of validation provides <see cref="IsValid"/> property and the list of <see cref="Errors"/>.
    /// It implements the composite pattern.
    /// </summary>
    public sealed class ValidationResult
    {
        private IEnumerable<ValidationError> thisErrors;
        private IEnumerable<ValidationResult> children;

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationResult"/> class as valid result.
        /// </summary>
        /// <param name="children">The children validation results.</param>
        public ValidationResult(IEnumerable<ValidationResult> children)
            : this(new ValidationError[0], children)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationResult"/> class 
        /// as invalid result with given errors.
        /// </summary>
        /// <param name="errors">The list of validation errors related to this result.</param>
        public ValidationResult(IEnumerable<ValidationError> errors)
            : this(errors, new ValidationResult[0])
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationResult"/> class
        /// as invalid result with given errors.
        /// </summary>
        /// <param name="errors">The list of validation errors related to this result.</param>
        /// <param name="children">The children validation results.</param>
        public ValidationResult(IEnumerable<ValidationError> errors, IEnumerable<ValidationResult> children)
        {
            this.children = children.Copy();
            this.thisErrors = errors.Copy();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationResult"/> class 
        /// as invalid result with given error.
        /// </summary>
        /// <param name="error">The validation error related to this result.</param>
        public ValidationResult(ValidationError error)
            : this(new[] { error }) 
        { 
        }

        /// <summary>
        /// Gets a value indicating whether this validation result represents valid result.
        /// </summary>
        /// <remarks>
        /// This value is calculated also from composite objects.
        /// </remarks>
        public bool IsValid
        {
            get
            {
                if (this.thisErrors.Any())
                {
                    return false;
                }

                foreach (var validationResult in this.children)
                {
                    if (!validationResult.IsValid)
                    {
                        return false;
                    }
                }

                return true;
            }
        }

        /// <summary>
        /// Gets the list of the validation errors.
        /// </summary>
        /// <remarks>
        /// This value is calculated also from composite objects.
        /// </remarks>
        public IEnumerable<ValidationError> Errors
        {
            get
            {
                var result = new List<ValidationError>();
                result.AddRange(this.thisErrors);

                foreach (var validationResult in this.children)
                {
                    result.AddRange(validationResult.Errors);
                }

                return result;
            }
        }

        /// <summary>
        /// Gets the instance that represents the positive result of validation.
        /// </summary>
        /// <returns>The instance of <see cref="ValidationResult"/> without any validation errors.</returns>
        public static ValidationResult GetValidResult()
        {
            return new ValidationResult(new ValidationResult[0]);
        }

        /// <summary>
        /// Writes the errors messages to given text writer.
        /// </summary>
        /// <param name="writer">The output writer.</param>
        public void WriteErrorsTo(TextWriter writer)
        {
            foreach (var error in this.Errors)
            {
                if (error.InvalidOptions.Count() == 1)
                {
                    writer.WriteLine(
                        "Error: option '{0}' is invalid.",
                        error.InvalidOptions.First().GetAllNames().First());
                }
                else
                {
                    writer.Write("Error: options ");
                    foreach (var option in error.InvalidOptions)
                    {
                        writer.Write(option.GetAllNames().First());
                    }

                    writer.WriteLine(" are invalid.");
                }

                writer.WriteLine("Message: {0}", error.Message);
                writer.WriteLine();
            }
        }

        /// <summary>
        /// Writes the errors messages to <see cref="Console.Out"/>.
        /// Overload of method <see cref="WriteErrorsTo(TextWriter)"/>.
        /// </summary>
        public void WriteErrorsToConsole()
        {
            this.WriteErrorsTo(Console.Out);
        }
    }
}
